// Copyright 2010 Christophe Henry
// henry UNDERSCORE christophe AT hotmail DOT com
// This is an extended version of the state machine available in the boost::mpl library
// Distributed under the same license as the original.
// Copyright for the original version:
// Copyright 2005 David Abrahams and Aleksey Gurtovoy. Distributed
// under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#include "benchmark.hpp"
#include <boost/statechart/event.hpp>
#include <boost/statechart/state_machine.hpp>
#include <boost/statechart/simple_state.hpp>
#include <boost/statechart/transition.hpp>
#include "boost/mpl/list.hpp"

namespace sc = boost::statechart;
namespace mpl = boost::mpl;

namespace test_sc {
// events
struct play : sc::event<play> {};
struct end_pause : sc::event<end_pause> {};
struct stop : sc::event<stop> {};
struct pause : sc::event<pause> {};
struct open_close : sc::event<open_close> {};
struct cd_detected : sc::event<cd_detected> {};
struct NextSong : sc::event<NextSong> {};
struct PreviousSong : sc::event<PreviousSong> {};

struct Empty;
struct Open;
struct Stopped;
struct Playing;
struct Paused;
// SM
struct player : sc::state_machine<player, Empty> {
  void open_drawer(open_close const&) { /*std::cout << "player::open_drawer\n";*/
  }
  void store_cd_info(cd_detected const& cd) { /*std::cout << "player::store_cd_info\n";*/
  }
  void close_drawer(open_close const&) { /*std::cout << "player::close_drawer\n";*/
  }
  void start_playback(play const&) { /*std::cout << "player::start_playback\n";*/
  }
  void stopped_again(stop const&) { /*std::cout << "player::stopped_again\n";*/
  }
  void stop_playback(stop const&) { /*std::cout << "player::stop_playback\n";*/
  }
  void pause_playback(pause const&) { /*std::cout << "player::pause_playback\n"; */
  }
  void stop_and_open(open_close const&) { /*std::cout << "player::stop_and_open\n";*/
  }
  void resume_playback(end_pause const&) { /*std::cout << "player::resume_playback\n";*/
  }
};

struct Empty : sc::simple_state<Empty, player> {
  Empty() {  /*std::cout << "entering Empty" << std::endl;*/
  }          // entry
  ~Empty() { /*std::cout << "leaving Empty" << std::endl;*/
  }          // exit
  typedef mpl::list<sc::transition<open_close, Open, player, &player::open_drawer>,
                    sc::transition<cd_detected, Stopped, player, &player::store_cd_info>> reactions;
};
struct Open : sc::simple_state<Open, player> {
  Open() {  /*std::cout << "entering Open" << std::endl;*/
  }         // entry
  ~Open() { /*std::cout << "leaving Open" << std::endl;*/
  }         // exit
  typedef sc::transition<open_close, Empty, player, &player::close_drawer> reactions;
};
struct Stopped : sc::simple_state<Stopped, player> {
  Stopped() {  /*std::cout << "entering Stopped" << std::endl;*/
  }            // entry
  ~Stopped() { /*std::cout << "leaving Stopped" << std::endl;*/
  }            // exit
  typedef mpl::list<sc::transition<play, Playing, player, &player::start_playback>,
                    sc::transition<open_close, Open, player, &player::open_drawer>,
                    sc::transition<stop, Stopped, player, &player::stopped_again>> reactions;
};
struct Song1;
struct Playing : sc::simple_state<Playing, player, Song1> {
  Playing() {  /*std::cout << "entering Playing" << std::endl;*/
  }            // entry
  ~Playing() { /*std::cout << "leaving Playing" << std::endl;*/
  }            // exit
  typedef mpl::list<sc::transition<stop, Stopped, player, &player::stop_playback>,
                    sc::transition<pause, Paused, player, &player::pause_playback>,
                    sc::transition<open_close, Open, player, &player::stop_and_open>> reactions;
  void start_next_song(NextSong const&) { /*std::cout << "Playing::start_next_song\n";*/
  }
  void start_prev_song(PreviousSong const&) { /*std::cout << "Playing::start_prev_song\n";*/
  }
};
struct Song2;
struct Song1 : sc::simple_state<Song1, Playing> {
  Song1() {  /*std::cout << "entering Song1" << std::endl;*/
  }          // entry
  ~Song1() { /*std::cout << "leaving Song1" << std::endl;*/
  }          // exit
  typedef sc::transition<NextSong, Song2, Playing, &Playing::start_next_song> reactions;
};
struct Song3;
struct Song2 : sc::simple_state<Song2, Playing> {
  Song2() {  /*std::cout << "entering Song2" << std::endl;*/
  }          // entry
  ~Song2() { /*std::cout << "leaving Song2" << std::endl;*/
  }          // exit
  typedef mpl::list<sc::transition<NextSong, Song3, Playing, &Playing::start_next_song>,
                    sc::transition<PreviousSong, Song1, Playing, &Playing::start_prev_song>> reactions;
};
struct Song3 : sc::simple_state<Song3, Playing> {
  Song3() {  /*std::cout << "entering Song3" << std::endl;*/
  }          // entry
  ~Song3() { /*std::cout << "leaving Song3" << std::endl;*/
  }          // exit
  typedef sc::transition<PreviousSong, Song2, Playing, &Playing::start_prev_song> reactions;
};
struct Paused : sc::simple_state<Paused, player> {
  Paused() {  /*std::cout << "entering Paused" << std::endl;*/
  }           // entry
  ~Paused() { /*std::cout << "leaving Paused" << std::endl;*/
  }           // exit
  typedef mpl::list<sc::transition<end_pause, Playing, player, &player::resume_playback>,
                    sc::transition<stop, Stopped, player, &player::stop_playback>,
                    sc::transition<open_close, Open, player, &player::stop_and_open>> reactions;
};
}

int main() {
  test_sc::player p;
  p.initiate();

  benchmark_execution_speed([&] {
    for (auto i = 0; i < 1'000; ++i) {
      p.process_event(test_sc::open_close());
      p.process_event(test_sc::open_close());
      p.process_event(test_sc::cd_detected());
      p.process_event(test_sc::play());
      for (auto j = 0; j < 1'000; ++j) {
        p.process_event(test_sc::NextSong());
        p.process_event(test_sc::NextSong());
        p.process_event(test_sc::PreviousSong());
        p.process_event(test_sc::PreviousSong());
      }

      p.process_event(test_sc::pause());
      // go back to Playing
      p.process_event(test_sc::end_pause());
      p.process_event(test_sc::pause());
      p.process_event(test_sc::stop());
      // event leading to the same state
      p.process_event(test_sc::stop());
      p.process_event(test_sc::open_close());
      p.process_event(test_sc::open_close());
    }
  });
  benchmark_memory_usage(p);
}
